import tensorflow as tf
import tensorflow.keras.layers as layers
import numpy as np

"""
模型的编译，确定训练流程
构建好模型后通过调用compile配置该模型的学习流程
compile(optimizer = "rmsprop",loss=None,metrice=None,loss_weights=None)

optimizer:优化器
loss 损失函数对于2分类任务就是交叉熵，回归任务就是mse之类的
metrics 在训练和测试期间的模型评估标准比如metrics=["accuracy"] 指定不同的评估标准，需要传递一个字典，如metrics={'output_a':'accuracy'}
loos_weights如果模型有多个任务输出，在优化全局loss的时候，需要给给个输出指定响应的权重
"""
model = tf.keras.Sequential();

model.add(layers.Dense(10,activation="softmax"))

# 学习力0.001，损失函数时交叉熵，使用经度作为依据
model.compile(optimizer=tf.keras.optimizers.Adam(0.01),
              loss=tf.keras.losses.categorical_crossentropy,
              metrics=[tf.keras.metrics.categorical_accuracy])

"""
模型的训练
使用的model.fit()或者是model.fit_generator()
对于小的数据常用的fit会将数据都存到内存之中
如果量比较大的话会将数据按批次读入
如果需要数据增强的话同样需要fit_generator
fit()的入参
x，输入的训练数据
y 目标（标签）数据
batch_size 每次梯度更新的样本数，如果没有指定默认为32
epochs 训练模型迭代轮次
verbose 日志展示模式 0=不显示，1=进度条。2= 每轮显示一行
callbacks 在训练是使用的回调函数
validation_split 验证集与训练数据的比例
validation_data 验证集，这个参数会覆盖validation_split

shuffle 是否在每轮迭代之前混洗数据，当steps_per_epoch非None时，这个参数无效
initial_epoch 开始训练的轮次，常用于恢复之前的训练权重
steps_per_epochs 数据集的大小/批次的大小 训练一轮需要更新多少次梯度
"""

train_x = np.random.random((1000, 36))
train_y = np.random.random((1000, 10))

val_x = np.random.random((200, 36))
val_y = np.random.random((200, 10))

# model.fit(train_x, train_y, batch_size=100, validation_data=(val_x, val_y))

"""
对于大型数据集可以使用tf.data构建训练输入
"""
dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
dataset = dataset.batch(32)
dataset = dataset.repeat()
val_dataset = tf.data.Dataset.from_tensor_slices((val_x, val_y))
val_dataset = val_dataset.batch(32)
val_dataset = val_dataset.repeat()

# model.fit(dataset, epochs=10, steps_per_epoch=30, validation_data=val_dataset, validation_steps=3)

"""
回调参数
回调函数传递给模型以自定义和扩展器在训练期间的行为的对象。我们可以编写自己的自定义回调，或者使用tf.keras.callbacks中的内置函数，常用内置回调函数如下
tf.keras.callbacks.ModelCheckPoint 定期保存模型
tf.keras.callbacks.LearningRateScheduler 动态更改学习率
tf.keras.callbacks.EarlyStopping 提前终止
tf.keras.callbacks.TensorBoard使用TensorBoard
自定义规则把他放到一个list中
"""
def lr_Scheduler(epoch):
    if  epoch > 0.9 * 10:
        lr = 0.0001
    elif epoch > 0.5 * 10:
        lr = 0.001
    elif epoch > 0.25 * 10:
        lr = 0.01
    else:
        lr = 0.1
    return lr

callbacks = [
    # 提前终止的配置
    tf.keras.callbacks.EarlyStopping(
        # 不再提升关注指标
        monitor='val_loos',
        # 不在提升的阈值
        min_delta=1e-2,
        # 不再提升的轮次
        patience=2
    ),
    # 定期保存模型
    tf.keras.callbacks.ModelCheckpoint(
        # 模型路径
        filepath='testmodel_{epoch}.h5',
        # 是否保存最佳模型
        save_best_only=True,
        # 监控指标
        monitor='val_loss'
    ),
    tf.keras.callbacks.LearningRateScheduler(lr_Scheduler)
]

model.fit(train_x,train_y,batch_size=16,epochs=10,callbacks=callbacks,validation_data=(val_x,val_y))

"""
模型的评估与预测
评估使用的是tf.keras.Model.evaluate()
预测使用的是tf.keras.Model.predict()
"""
test_x = np.random.random((1000, 36))
text_y = np.random.random((1000, 10))
model.evaluate(test_x,train_y,batch_size=32)

pre_x = np.random.random((10, 36))
result = model.predict(test_x,)
print(result)

"""
模型的保存于恢复
model.save()和model.save_weights()
model.save() 可以保存参数和结构
model.save_weights() 只保存权重
"""
model.save('G:/aa.h5')
new_model = tf.keras.models.load_model('G:/aa.h5')
new_prediction = new_model.predict(test_x)
np.testing.assert_allclose(result, new_prediction, atol=1e-6)

model.save_weights('G:/aa_weights.h5')
model.load_weights('G:/aa_weights.h5')
